#include "stdafx.h"
#include "GUISystem.h"
#include "GUIObject.h"
#include "InputManager.h"

#include "GameEngine.h"
using namespace std;

namespace ZeEngine
{
    GUISystem::GUISystem() : m_currentControl(NULL)
    {
    }

    GUISystem::~GUISystem()
    {
    }

    bool GUISystem::Init(GameEngine& gameEngine)
    {
        GUIObject::m_pResourceManager = &gameEngine.GetResourceManager();

        InputManager::AddMouseListener(this, "GuiSystemMouseListener");

        return true;
    }

    void GUISystem::Destroy(GameEngine& gameEngine)
    {
        GUIObject* pObject = NULL;

        for (auto pObject : m_controls)
        {
            pObject->Destroy();
        }

        m_controls.clear();

        InputManager::RemoveMouseListener("GuiSystemMouseListener");
    }

    bool GUISystem::Update(GameEngine& gameEngine)
    {
        bool fQuit = false;

        Vector2D<float> absolute = InputManager::GetAbsoluteMousePosition();
        GUIObject* pObject = NULL;
        
        bool fIntersetingControl = false;
        bool fNewFocusControl = false;

        for (auto pObject : m_controls)
        {
            if (pObject && !pObject->IsHidden())
            {
                bool fInside = Utilities::PointInRectangleTest(absolute, pObject->GetRectangle());

                if (fInside)
                {
                    fIntersetingControl = true;

                    //Only send the on mouse in event once when the mouse goes over
                    if (pObject != m_currentControl)
                    {
                        if (m_currentControl)
                        {
                            m_currentControl->OnMouseOut();
                        }

                        fNewFocusControl = true;
                        pObject->OnMouseIn();
                    }

                    m_currentControl = pObject;

                    pObject->OnMouseOver();
                }
            }

            //If nothing is being intersected, and we dont have a new control then send the event
            if (!fIntersetingControl && !fNewFocusControl && m_currentControl)
            {
                m_currentControl->OnMouseOut();
                m_currentControl = NULL;
            }

            if (fIntersetingControl)
            {
                break;
            }
        }

        pObject = NULL;

        for (auto pObject : m_controls)
        {
            if (pObject && !pObject->IsHidden())
            {
                fQuit |= pObject->Update();
            }
        }

        return fQuit;
    }

    void GUISystem::Render(SDL_Renderer& renderer, GameEngine& gameEngine, float interpolation)
    {
        GUIObject* pObject = NULL;

        //Iterate backwards over all controls, rendering those with low priority first
        for (auto& it = m_controls.rbegin(); it != m_controls.rend(); ++it)
        {
            pObject = it->get();
            if (pObject && !pObject->IsHidden())
            {
                Render(renderer, *pObject);
            }
        }
    }

    void GUISystem::Render(SDL_Renderer& renderer, GUIObject& object)
    {
        //Render it then render all of its children recursively
        object.Render(renderer);

        for (auto& child : object.m_children)
        {
            if (child)
            {
                Render(renderer, *child);
            }
        }
    }

    void GUISystem::AddControl(shared_ptr<GUIObject> pObject)
    {
        //Add the object to the front of the list to give it priority
        m_controls.push_front(pObject);
    }

  
    void GUISystem::RemoveControl(shared_ptr<GUIObject> pObject)
    {
        auto it = find(m_controls.begin(), m_controls.end(), pObject);

        if (it != m_controls.end())
        {
            m_controls.erase(it);
        }
    }

    shared_ptr<GUIObject> GUISystem::GetControl(u32 id) const
    {
        shared_ptr<GUIObject> pReturnObject = NULL;

        for (auto pObject : m_controls)
        {
            if (pObject->GetID() == id)
            {
                pReturnObject = pObject;
                break;
            }
        }

        return pReturnObject;
    }

    bool GUISystem::OnMouseMoved(const Vector2D<s32>& absolute, const Vector2D<s32>& relative)
    {
        return false;
    }
    
	bool GUISystem::OnMouseButtonPressed(const Vector2D<s32>& absolute, s32 button)
    {
        shared_ptr<GUIObject> pObject = NULL;
        bool fSendEvent = true;

        shared_ptr<GUIObject> pObjectOfFocus = NULL;

        //Only send the event to the highest one with the focus (which is ordered in the list)
        //TODO up this code whenever its very ugly
        for (auto it = m_controls.begin(); it != m_controls.end(); )
        {
            bool fIncrement = true;

            pObject = *it;
            if (pObject && !pObject->IsHidden())
            {

                if (fSendEvent)
                {
                    bool fInside = Utilities::PointInRectangleTest(absolute, pObject->GetRectangle());
                    if (fInside)
                    {
                        //Put this control to the front of the list since it now has focus, but only if it handles the button press
                        if (pObject->OnMouseButtonPressed(absolute, button))
                        {
                            pObjectOfFocus = pObject;
                            it = m_controls.erase(it);
                            fSendEvent = false;
                            fIncrement = false;
                        }
                    }

                    //The mouse button was released, we should send this event to everyone
                    pObject->OnMouseButtonDown(absolute, button);
                }
            }

            if (fIncrement)
            {
                ++it;
            }
        }

        if (!fSendEvent)
        {
            m_controls.push_front(pObjectOfFocus);
        }

        return false;
    }
    
	bool GUISystem::OnMouseButtonReleased(const Vector2D<s32>& absolute, s32 button)
    {
        GUIObject* pObject = NULL;

        bool fSendEvent = true;

        //Only send the event to the highest one with the focus (which is ordered in the list)
        for (auto pObject : m_controls)
        {
            if (pObject && !pObject->IsHidden())
            {
                if (fSendEvent)
                {
                    bool fInside = Utilities::PointInRectangleTest(absolute, pObject->GetRectangle());
                    if (fInside)
                    {             
                        //If the gui control handled the event, stop sending the event to the other gui controls
                        fSendEvent = !pObject->OnMouseButtonReleased(absolute, button);
                    }
                }

                //The mouse button was released, we should send this event to everyone
                pObject->OnMouseButtonUp(absolute, button);
            }
        }

        return false;
    }
}
